package com.zh.websocket;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.zh.config.WebSocketConfig;
import com.zh.domain.Model.ws.*;
import com.zh.domain.vo.user.UserVO;
import com.zh.service.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * websocket 服务端点。注意：websocket对象是多例的,tableId隔离用户操作范围
 */
@Component
@ServerEndpoint(value = "/ws/table/{tableId}/{userId}", configurator = WebSocketConfig.class, decoders = {WsMessageDecoder.class, TableMessageDecoder.class})
@Data
@Slf4j
public class TaskTableServer {

    // 用于存储每个表格-用户客户端对象 tableId-<userId-websocketServer>
    public static Map<Integer, Map<Integer, TaskTableServer>> onlineUserMap = new ConcurrentHashMap<>(10);
    // 表格在线人数
    public static Map<Integer, AtomicInteger> totalOnlineMap = new ConcurrentHashMap<>(10);
    // 表格在线用户列表
    public static Map<Integer, List<ConnectUserModel>> onlineUserListMap = new ConcurrentHashMap<>(10);
    // 当前用户id
    private volatile Integer userId;
    // 当前表格Id
    private volatile Integer tableId;
    //当前用户信息
    private volatile ConnectUserModel connectUser;
    // 当前会话
    private volatile Session session;

    //依赖注入
    private static UserService userService;

    private static WebsocketService websocketService;

    private static final String TYPE_OPEN = "open";

    private static final String TYPE_CLOSE = "close";

    private static final String OPERATION_UPDATE = "update";
    private static final String OPERATION_INIT = "init";
    private static final String MESSAGE_TYPE_TABLE = "table";
    private static final String MESSAGE_TYPE_EDIT = "edit";
    private static final String MESSAGE_TYPE_INIT = "init";

    @Resource
    public void setDeviceListenerService(UserService userService) {
        TaskTableServer.userService = userService;
    }

    @Resource
    public void setWebsocketService(WebsocketService websocketService) {
        TaskTableServer.websocketService = websocketService;
    }

    @OnOpen
    public void onOpen(Session session, @PathParam("tableId") Integer tableId, @PathParam("userId") Integer userId, EndpointConfig config) {
        this.tableId = tableId;
        this.userId = userId;
        this.session = session;
        onlineUserMap.computeIfAbsent(tableId, k -> new ConcurrentHashMap<>()).put(userId, this);
        totalOnlineMap.computeIfAbsent(tableId, k -> new AtomicInteger(0)).incrementAndGet();
        UserVO userVo = userService.getUserVoById(this.userId);
        if (userVo == null) {
            return;
        }
        ConnectUserModel connectUserModel = BeanUtil.copyProperties(userVo, ConnectUserModel.class);
        this.connectUser = connectUserModel;
        onlineUserListMap.computeIfAbsent(tableId, k -> new ArrayList<>()).add(connectUserModel);
        sendToAllWhenConnect();
        sendToMeWhenConnect();
    }


    @OnMessage
    public void onMessage(WsMessageModel wsMessageModel) {
        try {
            log.info("收到来自窗口[{}-{}]的信息: {}", this.tableId, this.session.getId(), wsMessageModel);
            String type = wsMessageModel.getType();
            switch (type) {
                case MESSAGE_TYPE_TABLE:
                    TableMessageModel tableMessageModel = wsMessageModel.getTableMessageModel();
                    handleTableMessage(tableMessageModel);
                    break;
                case MESSAGE_TYPE_EDIT:
                    EditInfoModel editInfoModel = wsMessageModel.getEditInfoModel();
                    handleEditMessage(editInfoModel);
                    break;
            }
        } catch (Exception e) {
            log.error("{} 连接-{}-接收消息时发生错误", this.userId, this.session.getId(), e);
        }
    }

    @OnClose
    public void onClose(Session session) {
        // 连接关闭
        Integer currentTableId = this.tableId;
        Integer currentUserId = this.userId;
        ConnectDataModel connectData = new ConnectDataModel();
        ConnectUserModel currentUser = this.connectUser;
        // 释放他的所有编辑权
        handleReleaseOnesEditingWhenClose(currentUserId);
        handleClose(currentTableId, currentUserId, connectData, currentUser);
        // 打印详细断开连接信息
        log.info("有一连接[{}-{}]关闭！当前表格[{}]的在线人数为 {}", currentUserId, session.getId(), currentTableId, totalOnlineMap.get(currentTableId));
    }

    private void handleReleaseOnesEditingWhenClose(Integer currentUserId) {
        List<EditReturnModel> editReturnModels = websocketService.clearUserEditStatus(currentUserId);
        for (EditReturnModel editReturnModel : editReturnModels) {
            WsReturnModel wsReturnModel = new WsReturnModel();
            wsReturnModel.setType(MESSAGE_TYPE_EDIT);
            wsReturnModel.setEditReturn(editReturnModel);
            // 发送消息给所有用户
            onlineUserMap.get(this.tableId).forEach((k, v) -> {
                if (!Objects.equals(k, this.userId)) {
                    v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnModel));
                }
            });
        }
    }

    private void handleClose(Integer currentTableId, Integer currentUserId, ConnectDataModel
            connectData, ConnectUserModel currentUser) {
        // 更新在线人数
        int currentTotalOnline = totalOnlineMap.get(currentTableId).decrementAndGet();
        // 更新在线用户列表
        onlineUserListMap.computeIfPresent(currentTableId, (tableId, userList) -> {
            userList.removeIf(user -> user.getId().equals(currentUserId));
            return userList;
        });
        List<ConnectUserModel> connectUserModelList = onlineUserListMap.get(currentTableId);
        // 发送关闭信息
        ConnectUserModel closedUser = BeanUtil.copyProperties(currentUser, ConnectUserModel.class);
        connectData.setConnectUser(closedUser);
        connectData.setOperation(TYPE_CLOSE);
        connectData.setTotalOnline(currentTotalOnline);
        connectData.setOnlineUserList(connectUserModelList);
        sendToAllWhenClosed(currentTableId, connectData);
        // 移除该用户session
        onlineUserMap.computeIfPresent(currentTableId, (tableId, userMap) -> {
            userMap.remove(currentUserId);
            return userMap;
        });
    }


    @OnError
    public void onError(Session session, Throwable throwable) {
        // 遍历释放的编辑权列表，向其他用户发送消息
        List<EditReturnModel> editReturnModels = websocketService.clearUserEditStatus(this.userId);
        for (EditReturnModel editReturnModel : editReturnModels) {
            WsReturnModel wsReturnModel = new WsReturnModel();
            wsReturnModel.setType(MESSAGE_TYPE_EDIT);
            wsReturnModel.setEditReturn(editReturnModel);
            // 发送消息给所有用户
            onlineUserMap.get(this.tableId).forEach((k, v) -> {
                if (!Objects.equals(k, this.userId)) {
                    v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnModel));
                }
            });
        }
        log.error("{} 连接-{}-发生错误", this.userId, this.session.getId(), throwable);
    }

    /**
     * 连接成功时获取当前表格编辑状态
     */
    private void sendToMeWhenConnect() {
        //发送给自己当前编辑权列表
        Map<String, SimpleUserModel> initEditStatus = websocketService.getCurrentEditStatus();
        WsReturnModel wsReturnModel = new WsReturnModel();
        wsReturnModel.setType(MESSAGE_TYPE_INIT);
        wsReturnModel.setInitEditStatus(initEditStatus);
        this.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnModel));
    }

    /**
     * 连接成功
     */
    private void sendToAllWhenConnect() {
        log.info(this.connectUser.getName() + "已连接表格编辑");
        AtomicInteger totalOnline = totalOnlineMap.get(this.tableId);
        List<ConnectUserModel> connectUserModelList = onlineUserListMap.get(this.tableId);
        ConnectDataModel connectData = new ConnectDataModel();
        connectData.setOperation(TYPE_OPEN);
        connectData.setConnectUser(this.connectUser);
        connectData.setTotalOnline(totalOnline.get());
        connectData.setOnlineUserList(connectUserModelList);
        onlineUserMap.get(this.tableId).forEach((k, v) -> {
            log.info("发送消息给用户-{}", k);
            v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(connectData));
            log.warn("连接成功发送消息-{}", connectData);
        });
        log.info("已广播通知所有连接-{}-当前在线人数和用户列表", this.tableId);
    }

    private void sendToAllWhenClosed(Integer currentTableId, ConnectDataModel connectData) {
        onlineUserMap.get(currentTableId).forEach((k, v) -> {
            if (!Objects.equals(k, connectData.getConnectUser().getId())) {
                log.info("发送消息给用户-{}-{}", k, connectData);
                v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(connectData));
                log.warn("关闭成功发送消息-{}-给用户-{}", connectData, k);
            }
        });
        log.info("已广播通知所有连接-{}-当前在线人数和用户列表", currentTableId);
    }

    private void handleTableMessage(TableMessageModel tableMessageModel) {
        Integer sendId = tableMessageModel.getSendId();
        String cellId = tableMessageModel.getCellId();
        if (Objects.isNull(sendId)) {
            log.error("消息体sendId为空{}", sendId);
            return;
        }
        Boolean success = websocketService.handleTableMessage(tableMessageModel);
        // 发送成功回送消息
        WsReturnModel wsReturnModel = new WsReturnModel();
        wsReturnModel.setType(MESSAGE_TYPE_TABLE);
        TableReturnModel tableReturn = new TableReturnModel();
        tableReturn.setCellId(cellId);
        tableReturn.setType(3);
        tableReturn.setOperation(tableMessageModel.getOperation());
        if (tableReturn.getOperation().equals(OPERATION_UPDATE)) {
            switch (tableMessageModel.getType()) {
                case 0: {
                    tableReturn.setContent(success ? "修改里程碑成功" : "修改里程碑失败");
                    break;
                }
                case 1: {
                    tableReturn.setContent(success ? "修改迭代成功" : "修改迭代失败");
                    break;
                }
                case 2: {
                    tableReturn.setContent(success ? "修改需求成功" : "修改需求失败");
                    break;
                }
                case 3: {
                    tableReturn.setContent(success ? "修改任务成功" : "修改任务失败");
                    break;
                }
                default:
                    tableReturn.setContent("未知操作");
                    break;
            }
        }
        if (tableReturn.getOperation().equals(OPERATION_INIT)) {
            tableReturn.setContent(success ? "初始化数据" : "初始化失败");
        }
        wsReturnModel.setTableReturn(tableReturn);
        // 广播给所有用户
        onlineUserMap.get(this.tableId).forEach((k, v) -> {
            v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnModel));
        });
    }

    private void handleEditMessage(EditInfoModel editInfoModel) {
        String status = editInfoModel.getStatus();
        //获取编辑权
        if (WebsocketService.EDIT_STATUS_EDITING.equals(status)) {
            handleEditing(editInfoModel);
        }
        //释放编辑权
        if (WebsocketService.EDIT_STATUS_RELEASE.equals(status)) {
            handleRelease(editInfoModel);
        }
    }

    /**
     * 处理获取编辑权
     *
     * @param editInfo 编辑信息
     */
    private void handleEditing(EditInfoModel editInfo) {
        boolean isEditing = websocketService.handleEditMessage(editInfo);
        //如果成功获取编辑权 发送给自己编辑权true表示可以编辑 否则false,发送给其他用户 当前单元格某用户正在编辑,不可编辑
        // 获取编辑权成功
        if (isEditing) {
            // 1. 通知发送者获取编辑权结果成功
            WsReturnModel wsReturnToMe = new WsReturnModel();
            wsReturnToMe.setType(MESSAGE_TYPE_EDIT);
            EditReturnModel editReturnToMe = new EditReturnModel();
            editReturnToMe.setResult(WebsocketService.EDIT_STATUS_EDITING);
            editReturnToMe.setEditInfo(editInfo);
            wsReturnToMe.setEditReturn(editReturnToMe);
            this.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnToMe));
            // 2. 通知其他用户该单元格被锁定
            WsReturnModel wsReturnToOthers = new WsReturnModel();
            wsReturnToOthers.setType(MESSAGE_TYPE_EDIT);
            EditReturnModel editReturnToOthers = new EditReturnModel();
            editReturnToOthers.setResult(WebsocketService.EDIT_STATUS_LOCKED);
            editReturnToOthers.setEditInfo(editInfo);
            wsReturnToOthers.setEditReturn(editReturnToOthers);
            log.info("在线人:{}", onlineUserMap.get(this.tableId));
            onlineUserMap.get(this.tableId).forEach((k, v) -> {
                if (!Objects.equals(k, this.userId)) {
                    log.info("发送消息给用户-{}-{}", k, wsReturnToOthers);
                    v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnToOthers));
                }
            });
        }
        //获取编辑权失败
        else {
            //1.通知发送者获取编辑权结果失败
            WsReturnModel wsReturnToMe = new WsReturnModel();
            wsReturnToMe.setType(MESSAGE_TYPE_EDIT);
            EditReturnModel editReturnToMe = new EditReturnModel();
            editReturnToMe.setResult(WebsocketService.EDIT_STATUS_DENIED);
            editReturnToMe.setEditInfo(editInfo);
            wsReturnToMe.setEditReturn(editReturnToMe);
            this.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnToMe));
        }
    }

    private void handleRelease(EditInfoModel editInfo) {
        boolean isReleased = websocketService.handleEditMessage(editInfo);
        //如果成功释放编辑权 发送给所有用户该单元格已释放
        if (isReleased) {
            WsReturnModel wsReturnToAll = new WsReturnModel();
            wsReturnToAll.setType(MESSAGE_TYPE_EDIT);
            EditReturnModel editReturnToAll = new EditReturnModel();
            editReturnToAll.setResult(WebsocketService.EDIT_STATUS_RELEASE);
            editReturnToAll.setEditInfo(editInfo);
            wsReturnToAll.setEditReturn(editReturnToAll);
            onlineUserMap.get(this.tableId).forEach((k, v) -> {
                v.session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wsReturnToAll));
            });
        }
    }

}